﻿using System;
using System.Collections.Generic;
#if L2S
using System.Data.Linq;
using System.Data.Linq.SqlClient;
#else
using ALinq.SqlClient;
#endif

namespace ALinq.Mapping
{
    /// <summary>
    /// Represents the mapping of a DataContext type to a database.
    /// </summary>
    /// <example>
    /// <code lang="c#">
    /// DatabaseMapping databaseMapping;
    /// TableMapping tableMapping;
    /// ColumnMapping columnMapping;
    /// PropertyInfo[] columnProperties;
    /// PropertyInfo associationProperty;
    /// AssociationMapping associationMapping;
    ///
    /// databaseMapping = new DatabaseMapping(typeof(NorthwindDatabase));
    /// tableMapping = new TableMapping(typeof(Category)) { Name = "Categories" };
    /// databaseMapping.Tables.Add(tableMapping);
    ///
    /// columnProperties = new[] { 
    ///                         typeof(Category).GetProperty("CategoryID"), 
    ///                         typeof(Category).GetProperty("CategoryName"),
    ///                         typeof(Category).GetProperty("Description"), 
    ///                         typeof(Category).GetProperty("Picture"),
    ///                     };
    /// associationProperty = typeof(Category).GetProperty("Products");
    /// foreach (var property in columnProperties)
    /// {
    ///     columnMapping = new ColumnMapping(property);
    ///     tableMapping.RowType.Columns.Add(columnMapping);
    /// }
    ///
    /// associationMapping = new AssociationMapping(associationProperty);
    /// associationMapping.ThisMember = new[] { typeof(Category).GetProperty("CategoryID") };
    /// tableMapping.RowType.Associations.Add(associationMapping);
    ///
    /// tableMapping = new TableMapping(typeof(Product)) { Name = "Products" };
    /// databaseMapping.Tables.Add(tableMapping);
    ///
    /// columnProperties = new[] { 
    ///                         typeof(Product).GetProperty("ProductID"), 
    ///                         typeof(Product).GetProperty("ProductName"),
    ///                         typeof(Product).GetProperty("CategoryID"), 
    ///                     };
    /// associationProperty = typeof(Product).GetProperty("Category");
    /// foreach (var property in columnProperties)
    /// {
    ///     columnMapping = new ColumnMapping(property);
    ///     tableMapping.RowType.Columns.Add(columnMapping);
    /// }
    /// associationMapping = new AssociationMapping(associationProperty);
    /// associationMapping.ThisMember = new[] { typeof(Product).GetProperty("CategoryID") };
    /// associationMapping.OtherMember = new[] { typeof(Category).GetProperty("CategoryID") };
    /// associationMapping.IsForeignKey = true;
    /// tableMapping.RowType.Associations.Add(associationMapping);
    /// var doc = DatabaseMappingSerializer.Serialize(databaseMapping);
    ///
    /// var mappingSource = XmlMappingSource.FromXml(doc.OuterXml);
    /// var model = mappingSource.GetModel(typeof(NorthwindDatabase));
    /// </code>
    /// </example>
    public class DatabaseMapping : ModelMapping
    {
        private ModelMappingCollection<TableMapping, Type> tables;
        private Type providerType;
        private Type contextType;

        /// <summary>
        /// Initializes a new instance of the ALinq.Mapping.DatabaseMapping class.
        /// </summary>
        /// <param name="contextType">A data-context type.</param>
        public DatabaseMapping(Type contextType)
            : base(ModelMappingType.Database)
        {
            Error.ValidateArgumentNotNull(() => contextType);
            Error.ValidateTypeImplementRequiredClass(contextType, typeof(DataContext));

            this.Name = contextType.Name;
            //this.Document.AppendChild(XmlElement);
            this.tables = new ModelMappingCollection<TableMapping, Type>(this, o => o.RowType.Type);
            this.contextType = contextType;
        }

        /// <summary>
        /// Gets or sets the name of the database.
        /// </summary>
        public string Name
        {
            get { return this.GetAttribute(() => Name); }
            set { this.SetAttribute(() => Name, value); }
        }

        /// <summary>
        /// Gets or sets the provider type.
        /// </summary>
        public Type ProviderType
        {
            get { return this.providerType; }
            set
            {
                if (value == null)
                    throw Error.MemberValueCanNotNull("ProviderType");

#if L2S
                if (value != typeof(SqlProvider) && !value.IsSubclassOf(typeof(SqlProvider)))
                    throw Error.InvalidProviderType(value);
#else
                if (!value.IsSubclassOf(typeof(SqlProvider)))
                    throw Error.InvalidProviderType(value);
#endif


                providerType = value;
                Provider = value.AssemblyQualifiedName;
            }
        }

        private string Provider
        {
            get { return this.GetAttribute(() => Provider); }
            set { this.SetAttribute(() => Provider, value); }
        }

        /// <summary>
        /// Gets the ALinq.Mapping.TableMapping associated with a specified System.Type.
        /// </summary>
        /// <param name="rowType">The common language runtime (CLR) row type.</param>
        /// <returns>A table mapping associated with the specified row type.</returns>
        public TableMapping GetTable(Type rowType)
        {
            foreach (var table in Tables)
            {
                var stack = new Stack<TypeMapping>();
                stack.Push(table.RowType);
                while (stack.Count > 0)
                {
                    var typeMapping = stack.Pop();
                    if (typeMapping.Type == rowType)
                        return table;

                    foreach (var child in typeMapping.DerivedTypes)
                        stack.Push(child);
                }
            }
            return null;

        }

        /// <summary>
        /// Get an enumeration of all tables.
        /// </summary>
        public ICollection<TableMapping> Tables
        {
            get { return this.tables; }
        }

        ///<summary>
        /// Type of the data-context.
        ///</summary>
        public Type ContextType
        {
            get { return this.contextType; }
        }

    }

    /// <summary>
    /// Represents the mapping of a DataContext type to a database.
    /// </summary>
    /// <typeparam name="TDataContext">The DataContext type.</typeparam>
    public class DatabaseMapping<TDataContext> : DatabaseMapping
    {
        /// <summary>
        /// Initializes a new instance of the ALinq.Mapping.DatabaseMapping&lt;TDataContext&gt; class.
        /// </summary>
        public DatabaseMapping()
            : base(typeof(TDataContext))
        {

        }
    }
}